//
//  BNAnimation.swift
//  BeepNow
//
//  Created by mikecrm-rt on 2019/11/11.
//  Copyright © 2019 RongTao. All rights reserved.
//

import UIKit
@objc protocol BNAnimationDelegate {
    @objc optional func animationDidStop(_ ani:BNAnimation)
    @objc optional func animationDidBegin(_ ani:BNAnimation)
}

class BNAnimation: NSObject {
    typealias aniBlock = (CGFloat)->Void
    var timer: CADisplayLink?
    var freq:Int = 60
    var delayValue:TimeInterval = 0
    var fromValue:CGFloat?
    var toValue:CGFloat?
    @objc var duration:Double = 0.0001
    private var lastPer = -1.0
    private var repeatCount:Int = 0
    private var isInfinite = false
    private var isOneFire = false
    var isPlayReverse:Bool = false
    private var timeFunction:CubicBezierFunction?
    private var startTimeMark:TimeInterval = 0
    var animationBlock:aniBlock!
    var completion:(()->Void)?
    private var addtionalCompletion:(()->Void)?
    var delegate:BNAnimationDelegate?
    var curTime:TimeInterval {
        get {
            let a = (self.timer?.timestamp ?? 0) - self.startTimeMark
            return a
        }
    }
    convenience init(delay:Double,action:@escaping aniBlock) {
        self.init()
        self.delayValue = delay
        self.animationBlock = action
        isOneFire = true
    }
    convenience init(duration:Double, from:CGFloat, to:CGFloat, timeFunction:CubicBezierFunction, anime:@escaping aniBlock) {
        self.init()
        self.duration = duration
        self.fromValue = from
        self.toValue = to
        self.timeFunction = timeFunction
        self.animationBlock = anime
    }
    @objc convenience init(duration:Double, from:CGFloat, to:CGFloat, controlPoint1:CGPoint, controlPoint2:CGPoint, anime:@escaping aniBlock) {
        self.init()
        self.fromValue = from
        self.toValue = to
        self.timeFunction = CubicBezierFunction.init(controlPoint1: controlPoint1, controlPoint2: controlPoint2)
        self.duration = duration
        self.animationBlock = anime
    }
    @objc func action() {
//        print(self,"dongdong",curTime,self.lastPer,self.repeatCount)
        guard self.duration != 0 else {
            return
        }
        guard self.startTimeMark != 0 else {
//            print("获取时间戳，不执行动画",self)
            startTimeMark = timer?.timestamp ?? -1
            return
        }
        
        guard lastPer < 1 && curTime <= duration + delayValue + 1 else {
            if self.repeatCount <= 0 {
                self.stop()
            } else {
                self.startTimeMark = 0
                self.lastPer = 0
                self.repeatCount -= 1
            }
//            delegate?.animationDidStop?(self)
            return
        }
        guard self.curTime >= delayValue else {
            return
        }
        if self.isInfinite {
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            self.animationBlock(CGFloat(curTime))
            CATransaction.commit()
        } else {
            let per = (curTime-self.delayValue)/self.duration
            lastPer = per
            if isOneFire && per < 1 {
                return
            }
            var value:CGFloat
//            if self.timeFunction != nil {
                value = self.timeFunction!.getYForX(CGFloat(per))
                if self.isPlayReverse {
                    value = timeFunction!.getYForX(CGFloat(1-per))
                }
//            }
            CATransaction.begin()
            CATransaction.setDisableActions(true)
            if self.fromValue != nil && self.toValue != nil {
                let length = toValue! - fromValue!
                let currentValue = value*length + fromValue!
                self.animationBlock(currentValue)
            } else {
                self.animationBlock(value)
            }
            CATransaction.commit()
            
        }
    }
    func playReverse() {
        self.isPlayReverse = true
        self.play {
            self.isPlayReverse = false
        }
    }
    @objc func play() {
//        print("play 开始动画",self,lastPer,self.startTimeMark)
        delegate?.animationDidBegin?(self)
            if self.timer == nil {
                timer = CADisplayLink.init(target: self, selector: #selector(action))
                if #available(iOS 10.0, *) {
                    timer?.preferredFramesPerSecond = freq
                }
            }
            startTimeMark = 0
            lastPer = 0
            self.timer?.add(to: .main, forMode: .common)
        
    }
    func play(_ completion:@escaping () -> Void) {
        self.addtionalCompletion = completion
        play()
    }
    func reset() {
        self.startTimeMark = 0
        lastPer = 0
//        print("reset aft",self.startTimeMark,self.lastPer)
    }
    func play(repeatCount:Int) {
        guard repeatCount > 0 else {
            return
        }
        self.repeatCount = repeatCount
        self.play()
    }
    func stop() {
        guard timer != nil else {
            return
        }
//        print("timer stop",self.curTime,self.isInfinite)
        
            timer?.invalidate()
            timer = nil
        
        self.completion?()
        self.addtionalCompletion?()
        self.addtionalCompletion = nil
        delegate?.animationDidStop?(self)
    }
    func cancel() {
        print("cancel")
        self.timer?.invalidate()
        self.timer = nil
    }
}
@objc protocol BNAnimationGroupDelegate {
    @objc optional func animationGroupDidStop(_ group:BNAnimationGroup)
    @objc optional func animationGroupDidBegin(_ group:BNAnimationGroup)
}
enum AnimationPlayType {
    case parallel
    case serial
    case lagging // 错帧
}
class BNAnimationGroup: NSObject,BNAnimationDelegate,BNAnimationGroupDelegate {
    
    var isPlayReverse = false
    var delay:Double = 0
    var instanceDelay:Double = 0
    private var controlTimer:CADisplayLink?
    var completion:(()->Void)?
    private var team:[BNAnimation] = []
    private var groupTeam:[BNAnimationGroup]! = []
    private var animations :[BNAnimation.aniBlock] = []
    var playCount:Int = 0
    var delegate:BNAnimationGroupDelegate?
    var playType:AnimationPlayType = .parallel
    var repeatCount:Int = 1
    private var reverseTeam :[BNAnimation] {
        get {
            return team.reversed()
        }
    }
    private var reverseGroupTeam:[BNAnimationGroup] {
        get {
            return groupTeam.reversed()
        }
    }
    var groupTeamCount: Int {
        get {
            if self.playType == .serial {
                return 0
            }
            var count = 0
            for group in self.groupTeam {
                count += group.aniCount
            }
            return count
        }
    }
    var aniCount:Int {
        get {
            return team.count + self.groupTeamCount
        }
    }
    convenience init(type:AnimationPlayType) {
        self.init()
        self.playType = type
    }
    convenience init(ani:BNAnimation) {
        self.init()
        ani.delegate = self
        team.append(ani)
    }
    convenience init(anis:[BNAnimation]) {
        self.init()
        for ani in anis {
            ani.delegate = self
            team.append(ani)
        }
    }
    func addAnimation(_ ani:BNAnimation) {
        ani.delegate = self
        if self.playType == .lagging {
            let group = BNAnimationGroup.init(ani: ani)
            group.delegate = self
            self.groupTeam.append(group)
        } else {
            team.append(ani)
        }
    }
    func addGroups(_ groups:[BNAnimationGroup]) {
        for group in groups {
            group.delegate = self
            self.groupTeam.append(group)
        }
    }
    func addAnis(_ anis:[BNAnimation]) {
        for ani in anis {
            ani.delegate = self
            if self.playType == .lagging {
                let group = BNAnimationGroup.init(ani: ani)
                group.delegate = self
                self.groupTeam.append(group)
            } else {
                team.append(ani)
            }
        }
    }
    
    func addGroup(_ group:BNAnimationGroup) {
        guard self.playType != .serial else {
            return
        }
        group.delegate = self
        groupTeam.append(group)
    }
    func playReverse() {
        guard team.count > 0 || groupTeam.count > 0 else {
            return
        }
        self.isPlayReverse = true
        self.playCount = 0
        switch playType {
        case .parallel:
            if self.groupTeam.count > 0 {
                for group in self.reverseGroupTeam {
                    group.playReverse()
                }
            }
            for ani in self.reverseTeam {
                ani.playReverse()
            }
        case .lagging:
            for i in 0...self.groupTeam.count - 1 {
                let group = self.groupTeam[self.groupTeam.count - 1 - i]
                let delayTimer = BNAnimation.init(delay: self.instanceDelay * Double(i)) { (val) in
                    group.playReverse()
                }
                delayTimer.play()
            }
        default:
            let ani = reverseTeam.first
            ani?.playReverse()
        }
    }
    private func playCore() {
        self.playCount = 0
        switch playType {
        case .parallel:
            if self.groupTeam.count > 0 {
                for group in self.groupTeam {
                    group.play()
                }
            }
            for ani in team {
                ani.play()
            }
        case .lagging:
            for i in 0...self.groupTeam.count - 1 {
                let group = self.groupTeam[i]
                let delayTimer = BNAnimation.init(delay: self.instanceDelay * Double(i)) { (val) in
                    group.play()
                }
                delayTimer.play()
            }
        default:
            let ani = team.first
            ani?.play()
        }
    }
    func play() {
        guard team.count > 0 || groupTeam.count > 0 else {
            return
        }
        
        if self.delay > 0 {
            let delayTimer = BNAnimation.init(delay: self.delay) { (val) in
//                print("delay",val)
                self.playCore()
            }
            delayTimer.play()
        } else {
            self.playCore()
        }
    }
    func play(_ completion:@escaping () -> Void) {
        self.completion = completion
        play()
    }
    func play(repeatCount:Int) {
        guard repeatCount > 0 else {
            return
        }
        let count = repeatCount == Int.max ? Int.max : repeatCount - 1
        self.repeatCount = count
        self.completion = {
            self.play(repeatCount: count)
        }
        self.play()
    }

    func cancel() {
        print("cancel",self.groupTeam.count,self)
        switch self.playType {
        case .serial:
            if playCount >= team.count {
                return
            }
            team[playCount].cancel()
        default:
            for ani in team {
                ani.cancel()
            }
            for group in groupTeam {
                group.cancel()
            }
            if self.repeatCount == Int.max {
                self.completion = nil
            }
        }
    }
    func animationDidBegin(_ ani: BNAnimation) {
        var onPlayTeam : [BNAnimation] = team
        if self.isPlayReverse {
            onPlayTeam = team.reversed()
        }
        switch self.playType {
        case .serial:
            if ani == onPlayTeam.first {
                delegate?.animationGroupDidBegin?(self)
            }
        default:
            if ani == onPlayTeam.first {
                delegate?.animationGroupDidBegin?(self)
            }
        }
        
    }
    func animationDidStop(_ ani: BNAnimation) {
        self.playCount += 1
//        print(ani,"完成",playCount)
        var onPlayTeam = team
        if self.isPlayReverse {
            onPlayTeam = team.reversed()
        }
        switch self.playType {
        case .serial:
            if ani == onPlayTeam.last {
                didStop()
            }
            if onPlayTeam.count < 2 {
                return
            }
            for i in 0...onPlayTeam.count - 2 {
                let curAni = onPlayTeam[i]
                if curAni == ani {
                    if self.isPlayReverse {
                        onPlayTeam[i+1].playReverse()
                    } else {
                        onPlayTeam[i+1].play()
                    }
                    break
                }
            }
        default:
            if self.playCount == self.team.count + self.groupTeam.count {
                didStop()
            }
        }
    }
    func animationGroupDidBegin(_ group: BNAnimationGroup) {
        var onPlayGroupTeam = groupTeam
        if self.isPlayReverse {
           onPlayGroupTeam = groupTeam.reversed()
        }
        if group == onPlayGroupTeam!.first {
            delegate?.animationGroupDidBegin?(self)
        }
    }
    
    func animationGroupDidStop(_ group: BNAnimationGroup) {
        self.playCount += 1
//        print("GroupDidStop",playCount)
        if self.playCount == self.team.count + self.groupTeam.count {
            didStop()
        }
    }

    func didStop() {
//        print("动画全部结束",self.playType,self.team.count + self.groupTeam.count)
        delegate?.animationGroupDidStop?(self)
        self.completion?()
        if self.isPlayReverse == true {
            self.isPlayReverse = false
        }
    }
    
}
